14 函数基本概念与实践
- 函数(functions)是 Go 语言最基本的构建块之一,用于封装一段可以复用的代码。
- 函数可以接受输入参数并返回结果,可以有返回值,也可以没有返回值。
- Go 的函数设计灵活,支持多返回值、匿名函数、函数作为参数传递等高级特性。
| 特性 | 说明 |
|---|---|
| 基本函数定义 | 使用 func 关键字定义,支持参数和返回值。 |
| 多返回值 | Go 支持返回多个值,常用于处理错误。 |
| 匿名函数 | 没有名字的函数,支持赋值给变量或作为闭包使用。 |
| 可变参数 | 使用 ... 定义可变参数,允许传递不定数量的参数。 |
| 闭包 | 函数引用外部变量,形成闭包,保持对外部变量的访问。 |
| 递归函数 | 函数可以调用自身,形成递归。 |
| defer | 延迟执行,常用于资源清理。 |
| 函数作为参数 | 函数可以作为参数传递给其他函数,实现更灵活的代码结构。 |
| 方法 | Go 中的方法是带有接收者的函数,附属于特定类型。 |
函数的定义
go
func functionName(parameter1 type1, parameter2 type2) returnTypeList {
// 函数体
// 可以包含多条语句
return result
}func:关键字,表示这是一个函数。functionName:函数名,标识这个函数的名称(如果是匿名函数,这里不需要名字)。函数名应该是有意义的,并且遵循 Go 语言的命名规范(驼峰命名法)。parameter1 type1, parameter2 type2:参数列表,表示传递给函数的参数,包含参数的名称和类型,多个参数之间用逗号分隔。returnTypeList:返回值类型,表示函数的返回类型。如果函数没有返回值,这部分可以省略。{}:函数体,包含了函数的逻辑实现。return语句用于返回结果。如果函数有返回值,return语句后面必须跟一个返回值。
go
package main
import "fmt"
// 定义一个简单的函数
func add(a int, b int) int {
return a + b
}
func main() {
result := add(3, 5) // 调用函数
fmt.Println(result) // 输出:8
}函数参数
函数可以接受零个或多个参数。参数可以是基本类型(如 int, string),也可以是复合类型(如 struct, slice)。
单一参数
可以给函数传递一个或多个参数,每个参数都需要指定类型。
go
func greet(name string) {
fmt.Println("Hello", name)
}多个参数
多个参数类型相同时,可以省略中间的类型声明,只在最后一次写类型:
go
// func add(a int, b int) int {
// a 和 b 都是 int 类型,因此可以简写为 a, b int
func add(a, b int) int {
return a + b
}可变参数
- Go 支持可变参数(variadic parameters),也就是传递不定数量的参数。
- 使用
...来表示可变参数,函数内部将这些参数作为切片(slice)处理。
go
// sum 函数接受任意数量的 int 类型参数,并将它们相加后返回总和
func sum(numbers ...int) int {
total := 0
for _, number := range numbers {
total += number
}
return total
}
func main() {
fmt.Println(sum(1, 2, 3)) // 输出:6
fmt.Println(sum(5, 10, 15, 20)) // 输出:50
}函数返回值
函数可以返回零个或多个值。返回值的类型在函数定义时指定。
单一返回值
go
// multiply 函数接受两个 int 类型的参数 a 和 b,并返回它们的乘积
func multiply(a, b int) int {
return a * b
}
// square 函数接受一个 int 类型的参数 x,并返回 x 的平方
func square(x int) int {
return x * x
}多返回值
- Go 允许函数返回多个值,这在错误处理和函数返回多个结果时特别有用。
- 多个返回值用逗号分隔,返回值的类型在函数定义时指定。
go
// divide 函数接受两个 int 类型的参数 a 和 b,并返回它们的商和余数 (int 类型)
func divide(a, b int) (int, int, error) {
if b == 0 {
return 0, 0, fmt.Errorf("division by zero")
}
quotient := a / b
remainder := a % b
return quotient, remainder, nil
}
func main() {
quotient, remainder, err := divide(10, 3)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Quotient:", quotient, "Remainder:", remainder)
}
}命名返回值
函数的返回值可以被命名,使得返回值可以在函数体内作为变量使用,并且最后可以通过 return 直接返回,无需明确指定返回值。
go
// swap 函数接受两个 string 类型的参数 a 和 b,并返回两个 string 类型的值
// quotient 和 remainder 是命名返回值。在函数体内可以直接使用这两个变量,并且在 return 语句中可以省略返回值
func swap(a, b string) (x, y string) {
x = b
y = a
return
}
// divide 函数接受两个 int 类型的参数 a 和 b,并返回两个 int 类型的值
func divide(a, b int) (quotient, remainder int, err error) {
if b == 0 {
return 0, 0, fmt.Errorf("division by zero")
}
quotient = a / b
remainder = a % b
return
}
func main() {
fmt.Println(swap("hello", "world")) // 输出:world hello
quotient, remainder, err := divide(10, 3)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Quotient:", quotient, "Remainder:", remainder)
}
}函数作为值和参数
- 函数是一等公民(first-class citizen),这意味着函数可以作为值传递、赋值给变量、作为参数传递给其他函数,甚至可以作为返回值返回。
函数类型
函数可以定义为一种类型,这种类型可以用来声明变量、作为参数传递等。
go
// operation 是一个函数类型,它接受两个 int 类型的参数并返回一个 int 类型的值
type operation func(int, int) int
// add 和 multiply 函数符合这个类型,因此可以赋值给 op 变量
func add(a, b int) int {
return a + b
}
func multiply(a, b int) int {
return a * b
}
func main() {
var op operation
op = add
fmt.Println(op(3, 5)) // 输出:8
op = multiply
fmt.Println(op(3, 5)) // 输出:15
}函数作为参数
go
type operation func(int, int) int
func add(a, b int) int {
return a + b
}
func multiply(a, b int) int {
return a * b
}
// applyOperation 函数接受一个函数类型的参数 op,以及两个 int 类型的参数 a 和 b
// applyOperation 函数调用传递的函数 op,并返回结果
func applyOperation(op func(int, int) int, a, b int) int {
return op(a, b)
}
func main() {
result := applyOperation(add, 3, 5)
fmt.Println(result) // 输出:8
result = applyOperation(multiply, 3, 5)
fmt.Println(result) // 输出:15
}go
// apply 函数接受一个 operation 类型的参数 op,以及两个 int 类型的参数 a 和 b
// 调用传递的函数 op 并返回结果
func apply(op operation, a, b int) int {
return op(a, b)
}
func main() {
add := func(x, y int) int {
return x + y
}
result := apply(add, 3, 5)
fmt.Println(result) // 输出:8
multiply := func(x, y int) int {
return x * y
}
result = apply(multiply, 3, 5)
fmt.Println(result) // 输出:15
}函数作为返回值
go
type operation func(int, int) int
func add(a, b int) int {
return a + b
}
func multiply(a, b int) int {
return a * b
}
// getOperation 函数根据传入的字符串参数返回不同的函数
func getOperation(op string) func(int, int) int {
if op == "add" {
return add
} else if op == "multiply" {
return multiply
}
return nil
}
func main() {
// 调用 getOperation 函数获取相应的操作函数,并调用该函数
op := getOperation("add")
fmt.Println(op(3, 5)) // 输出:8
op = getOperation("multiply")
fmt.Println(op(3, 5)) // 输出:15
}go
// multiplier 函数接受一个 int 类型的参数 factor,并返回一个函数
// 返回的函数接受一个 int 类型的参数 x,并返回 x 乘以 factor 的结果
func multiplier(factor int) func(int) int {
return func(x int) int {
return x * factor
}
}
func main() {
double := multiplier(2)
fmt.Println(double(5)) // 输出:10
}匿名函数
- 匿名函数(anonymous function)是没有名字的函数。
- 它们可以作为表达式使用,赋值给变量,或作为参数传递给其他函数。
- 匿名函数在 Go 语言中也可以形成闭包。
- 匿名函数可以直接定义并使用,通常用于短小的函数逻辑。
基本匿名函数
go
func main() {
// 定义并调用匿名函数
func(name string) {
fmt.Println("Hello", name)
}("Go") // 输出:Hello Go
func(a, b int) int {
return a * b
}(5, 6) // 输出:30
}赋值给变量
go
func main() {
greet := func(name string) {
fmt.Println("Hello", name)
}
greet("Go") // 输出:Hello Go
}闭包
闭包(closure)是指一个函数引用了其外部作用域中的变量,并且这个函数在外部作用域结束后仍然可以访问这些变量。
go
// adder 函数返回一个闭包。闭包捕获了 sum 变量,并在每次调用时累加 sum 的值
// 匿名函数引用了外部的 `sum` 变量,形成了闭包,能够在函数调用结束后继续访问并修改 `sum` 的值
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main() {
pos := adder()
fmt.Println(pos(1)) // 输出:1
fmt.Println(pos(2)) // 输出:3
fmt.Println(pos(3)) // 输出:6
}递归函数
- 函数可以调用自身,形成递归。
- 递归函数通常需要一个基本条件来终止递归。
go
// 计算阶乘
func factorial(n int) int {
if n == 0 {
return 1
}
return n * factorial(n-1)
}
func main() {
fmt.Println(factorial(5)) // 输出:120
}延迟调用 defer
defer用于延迟函数的执行,通常在函数结束时执行。- 即使函数中途发生了
panic,defer也会确保被调用。 - 常用于清理操作,例如资源释放、关闭文件、释放资源等。
go
func main() {
defer fmt.Println("World") // 在 main 函数返回之前执行
fmt.Println("Hello")
}
// 输出:
// Hello
// World错误处理
Go 语言的函数通常会返回一个错误(error)类型的值,用于表示函数执行过程中是否发生了错误。
go
// divide 函数在除数为零时返回一个错误。main 函数检查错误并进行相应的处理
func divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("division by zero")
}
return a / b, nil
}
func main() {
result, err := divide(10, 0)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", result)
}
}方法与函数的区别
在 Go 中,方法(method)是带有接收者的函数,接收者可以是结构体或其他自定义类型。方法的定义与普通函数类似,但它附属于某个类型。
go
type Person struct {
Name string
}
// 定义方法:greet 是 Person 类型的方法,接受者 p 是这个方法的调用对象
func (p Person) greet() {
fmt.Println("Hello,", p.Name)
}
func main() {
p := Person{Name: "Alice"}
p.greet() // 输出:Hello, Alice
}go
type Rectangle struct {
width, height int
}
// 定义方法
func (r Rectangle) Area() int {
return r.width * r.height
}
// 使用方法
rect := Rectangle{width: 10, height: 5}
area := rect.Area() // 返回 50实验代码
go
package main
import (
"fmt"
"io/ioutil"
"os"
)
// 从文件中读取内容
func readFile(filename string) (string, error) {
// content, err := os.ReadFile(filename)
// if err != nil {
// return "", err
// }
file, err := os.Open(filename)
if err != nil {
// defer file.Close()
_ = file.Close()
return "", err
}
content, err := ioutil.ReadFile(filename)
if err != nil {
return "", err
}
return string(content), nil
}
func main() {
// 匿名函数
func(filename string) {
content, err := readFile(filename)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(content)
}("README.md")
content, err := readFile("test.txt")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(content)
}
// 输出:
// README
// open test.txt: The system cannot find the file specified.